package simulador;

import java.util.ArrayList;
import javax.swing.JPanel;
import smallsimulator.chegadaInfo;
import smallsimulator.saidaInfo;
import smallsimulator.servicoInfo;
import smallsimulator.servicoPanel;
import smallsimulator.spliterInfo;
import smallsimulator.spliterPanel;

public class Simulador {

    // Relógio de simulação - variável que contém o valor do tempo em cada instante
    private double instante;
    // Médias das distribuições de chegadas e de atendimento no serviço
    private double media_cheg, media_serv;
    // Número de clientes que vão ser atendidos
    private int valor_atingir;
    private int n_clientes_atendidos;
    // Serviço - pode haver mais do que um num simulador
    private Servico servico;
    // Lista de eventos - onde ficam registados todos os eventos que vão ocorrer na simulação
    // Cada simulador só tem uma
    private ListaEventos lista;
    private ArrayList<Object> sequencia;
    //0 = unidades, 1 = tempo
    private int tipoFim = 0;
    private ArrayList<ServicoStats> serStats;
    private ArrayList<Servico> servicosIds;
    private double lastSave = 0;
    private double saveInterval = 1;

    // Construtor
    /*public Simulador() {
    // Inicialização de parâmetros do simulador
    media_cheg = 1;
    media_serv = 1.5;
    n_clientes = 100;
    // Inicialização do relógio de simulação
    instante = 0;
    // Criação do serviço
    servico = new Servico(this);
    // Criação da lista de eventos
    lista = new ListaEventos(this);
    // Agendamento da primeira chegada
    // Se não for feito, o simulador não tem eventos para simular
    insereEvento(new Chegada(instante, this));
    }*/
    /*
     * O ArrayList de Objectos deve conter uma sequência de objectos do tipo Info (chegadaInfo, servicoInfo, saidaInfo, spliterInfo)
     * de forma a ser gerada a lista de eventos correctamente.
     */
    public Simulador(ArrayList<Object> infos) {
        media_cheg = 1;
        media_serv = 1.5;
        //n_clientes = 100;
        n_clientes_atendidos = 0;
        // Inicialização do relógio de simulação
        instante = 0;

        //ArrayLists para guardar os valores que a simulação produzir ao longo do tempo.
        serStats = new ArrayList<ServicoStats>();
        //auxiliar
        servicosIds = new ArrayList<Servico>();

        //Sort dos diversos tipos de eventos para contrução da sequência
        int splitters = 0;
        sequencia = new ArrayList<Object>();
        for (int i = 0; i < infos.size(); i++) {
            if (infos.get(i).getClass() == chegadaInfo.class) {
                sequencia.add(infos.get(i));
            } else if (infos.get(i).getClass() == saidaInfo.class) {
                sequencia.add(infos.get(i));
            } else if (infos.get(i).getClass() == servicoInfo.class) {
                sequencia.add(new Servico(this, (servicoInfo) infos.get(i), i));
                servico = (Servico) sequencia.get(sequencia.size() - 1);
                servicosIds.add(servico);
                serStats.add(new ServicoStats(((servicoInfo) infos.get(i)).getNome()));
            } else if (infos.get(i).getClass() == spliterInfo.class) {
                sequencia.add(new Splitter(this, (spliterInfo) infos.get(i), "Splitter " + (splitters++), i));
            }
        }

        //primeira leitura dos valores iniciais em cada serviço
        for (int i = 0; i < sequencia.size(); i++) {
            if (sequencia.get(i).getClass() == Servico.class) {
                Servico tempss = ((Servico) sequencia.get(i));
                lastSave = lastSave + saveInterval;
                ((ServicoStats) this.serStats.get(this.servicosIds.indexOf(tempss))).addVals((float) tempss.getFilaMax(), (float) tempss.getFilaS(), (float) tempss.getTempoMed(), (float) tempss.getCompMed(), (float) tempss.getUtil(), (float) tempss.getAtendidos());

            }
        }
        // Criação do serviço
        //servico = new Servico(this);
        // Criação da lista de eventos
        lista = new ListaEventos(this);

        //Determinação do tipo de finalização (unidades ou tempo) e o valor a atingir
        tipoFim = ((chegadaInfo) infos.get(0)).getTipo();
        valor_atingir = ((chegadaInfo) infos.get(0)).getValor();
        //System.out.println("valor atingir "+valor_atingir);
        // Agendamento da primeira chegada
        // Se não for feito, o simulador não tem eventos para simular
        insereEvento(new Chegada(instante, this, (chegadaInfo) infos.get(0)));
    }

    // programa principal
    /*public static void main(String[] args) {
    // Cria um simulador e
    Simulador s = new Simulador();
    // põe-o em marcha
    s.executa();
    }*/
    // Método que insere o evento e1 na lista de eventos
    void insereEvento(Evento e1) {
        lista.insereEvento(e1);
    }

    //transfere um cliente para o primeiro evento da sequência
    void transitaCliente(Cliente cli) {
        transitaCliente(cli, 0);
    }

    //guarda os valores actuais de todos os serviços
    void saveCurrStats() {
        if (instante >= lastSave) {
            for (int i = 0; i < sequencia.size(); i++) {
                if (sequencia.get(i).getClass() == Servico.class) {
                    Servico tempss = ((Servico) sequencia.get(i));

                    ((ServicoStats) this.serStats.get(this.servicosIds.indexOf(tempss))).addVals((float) tempss.getFilaMax(), (float) tempss.getFilaS(), (float) tempss.getTempoMed(), (float) tempss.getCompMed(), (float) tempss.getUtil(), (float) tempss.getAtendidos());

                }
            }
            lastSave = lastSave + saveInterval;
        }
    }

    //transfere um cliente para um evento na sequência de eventos
    void transitaCliente(Cliente cli, int destIdx) {


        if (destIdx < 0) {
            destIdx = destIdx * (-1);
        } else if (sequencia.get(destIdx - 1).getClass() == Servico.class) {
            ((Servico) sequencia.get(destIdx - 1)).removeServico();
        }

        if (sequencia.get(destIdx).getClass() == Servico.class) {

            ((Servico) sequencia.get(destIdx)).insereServico(cli);


        } else if (sequencia.get(destIdx).getClass() == Splitter.class) {
            ((Splitter) sequencia.get(destIdx)).insereSplitter(cli);
        } else {
            n_clientes_atendidos++;
        }

    }

    // Método que actualiza os valores estatísticos do simulador
    private void act_stats() {
        for (int i = 0; i < sequencia.size(); i++) {
            if (sequencia.get(i).getClass() == Servico.class) {
                ((Servico) sequencia.get(i)).act_stats();
            }
        }
    }

    // Método que apresenta os resultados de simulação finais
    public String getRelat() {
        String retorno = "";
        retorno += "\n";
        retorno += "------- Resultados finais -------" + "\n";
        retorno += "\n";
        for (int i = 0; i < sequencia.size(); i++) {
            if (sequencia.get(i).getClass() == Servico.class) {
                retorno += ((Servico) sequencia.get(i)).getRelatFinal();
            }
        }
        return retorno;
    }

    private void relat() {
        System.out.println();
        System.out.println("------- Resultados finais -------");
        System.out.println();
        for (int i = 0; i < sequencia.size(); i++) {
            if (sequencia.get(i).getClass() == Servico.class) {
                ((Servico) sequencia.get(i)).relat();
            }
        }
    }

    // Método executivo do simulador
    public void executa() {
        Evento e1;
        int ciclos = 0;
        // Enquanto não atender todos os clientes
        while ((tipoFim == 0 && n_clientes_atendidos < valor_atingir) || (tipoFim == 1 && instante < valor_atingir)) {

            ciclos++;
            //pequena alteração para permitir guardar os dados de cada serviço de forma regular ao longo do tempo (1 em 1 minuto)
            if (instante < ((Evento) lista.getFirst()).getInstante() - 1) {
                instante = instante + 1;
                act_stats();
                saveCurrStats();
            } else {

                e1 = (Evento) (lista.removeFirst());  // Retira primeiro evento (é o mais iminente) da lista de eventos
                instante = e1.getInstante();         // Actualiza relógio de simulação
                act_stats();                         // Actualiza valores estatísticos
                saveCurrStats();
                e1.executa(servico);                 // Executa evento
            }
        }
        System.out.println("ciclos " + ciclos);
        relat();  // Apresenta resultados de simulação finais
    }

    // Método que devolve o instante de simulação corrente
    public double getInstante() {
        return instante;
    }

    // Método que devolve a média dos intervalos de chegada
    public double getMedia_cheg() {
        return media_cheg;
    }

    // Método que devolve a média dos tempos de serviço
    public double getMedia_serv() {
        return media_serv;
    }

    public ArrayList<ServicoStats> getSerStats() {
        System.out.println("total " + ((ServicoStats) serStats.get(0)).getFilamax().size());
        return serStats;
    }

    public void setSerStats(ArrayList<ServicoStats> serStats) {
        this.serStats = serStats;
    }

    public ArrayList<Servico> getServicosIds() {
        return servicosIds;
    }

    public void setServicosIds(ArrayList<Servico> servicosIds) {
        this.servicosIds = servicosIds;
    }
}